<template>
  <div class="ee-upload fc">
    <!-- 预览 -->
    <ul class="fc" v-if="__modelValue.length">
      <li class="item preview" v-for="item in __modelValue" :key="item.url">
        <slot name="preview" :i="item">
          <img :src="item.url" :alt="item.name" :style="{objectFit:objectFit}" />
        </slot>
        <div v-if="item.upload">
          <div class="action-mask" style="opacity:.5"></div>
          <div class="actions fc" style="width:100%;height:100%">
            <ee-progress v-model="item.progress" style="font-size:14px;color:#fff"></ee-progress>
            <!-- <span style="color:#fff">{{Math.round(item.progress * 100)}}%</span> -->
          </div>
        </div>
        <div v-else>
          <div class="action-mask hidden"></div>
          <div class="fc actions hidden">
            <i class="action iconfont icon-iconfontok" @click="ePreview(item)"></i>
            <i class="action iconfont icon-iconfontok" @click="eDownload(item)"></i>
            <i class="action iconfont icon-iconfontok" @click="eDelete(item)"></i>
          </div>
        </div>
      </li>
    </ul>
    <!-- 上传和预览的中间 -->
    <slot name="space"></slot>
    <div v-if="canUpload" class="item upload fc"
      tabindex="0"
      @click="$refs.u.click()"
      @keydown.enter="$refs.u.click()"
      @dragover.prevent @drop="eFileDrop" @drop.prevent>
      <!-- 上传样式默认图片框 -->
      <slot>
        <span class="ee-upload-icon">+</span>
      </slot>
      <input ref="u" type="file" :name="name" :accept="accept" :multiple="multiple" @change="eFileSelect" />
    </div>
  </div>
</template>

<script type="text/javascript">
export function UploadFile(url, key, file, onprogress, onsuccess, onerror) {
  var req = new XMLHttpRequest();
  req.onreadystatechange = function() {
    if (req.readyState == 4 && req.status == 200)
      onsuccess(req.responseText);
  }
  req.open("POST", url, true);
  req.responseType = "text";
  req.onerror = onerror;
  req.upload.onprogress = onprogress;

  let form = new FormData();
  form.append(key, file);
  req.send(form);
  return req;
}
export async function DataToFile(data) {
  if (!data)
    return undefined;
  if (data.constructor == File)
    return data;
  let blob;
  if (data.constructor == HTMLCanvasElement)
    blob = (data.msToBlob | data.toBlob)();
  else if (data.constructor == Blob)
    blob = data;
  else if (data.constructor == Image)
    ; // todo: 用canvas绘制 | 用网络流下载
  else if (data.constructor == String)
    ; // todo: base64String
  if (!blob)
    return undefined;
  // todo: 通过Blob构建File实例，但是这里还缺少文件名和文件类型
}
function FileRead(newfile, func) {
  return new Promise(async (resolve) => {
          let ret = func();
          if (ret) {
            if (ret.constructor == Promise) {
              await ret.then(i => {
                newfile.file = DataToFile(i);
              }).catch(() => {
                newfile.file = undefined;
              });
            }
          } else if (ret == false) {
            newfile.file = undefined;
          }
          resolve();
        })
}
export default injectRender({
  name: "ee-upload",
  emits: ["onErrorSelect", "onErrorRead"],
  props: {
    /** 已经上传的文件列表数组，类型包含以下字段，其余字段可以自行添加
     * name: 文件名，可用于上传后预览展示
     * url: 图片路径，可用于上传后预览展示
     * value: 上传文件到服务器返回的结果
     * progress: 1-100，代表上传时的进度
     * 
     * modelValue也可以是一个String[]，代表所有的value，预览url可以传入到preview数组
     * 传入String[]后modelValue会被改变成Object[]
     */
    modelValue: {
      type: Array,
      default: () => [],
      set(value) {
        if (!value || !(value instanceof Array)) {
          value = [];
          this.__modelValue = value;
        }
        // value是字符串数组时，代表对象value的数组
        for (let i = 0; i < value.length; i++)
          if (value[i].constructor == String)
            value[i] = { name: value[i], value: value[i] };
      }
    },
    /** 我的C#实现方式是preview一个数组，value一个数组，可以考虑v-model="value" :preview="preview" */
    preview: {
      type: Array,
      default: () => [],
      set(value) {
        if (!value || !(value instanceof Array))
          this.__preview = [];

        if (this.__preview.length != this.__modelValue.length)
          throw "预览图数量必须和modelValue数量一样";

        for (let i = 0; i < this.__modelValue; i++)
          this.__modelValue[i].url = value[i];
      }
    },
    /** todo:预览在上传的后面 */
    right: Boolean,
    /** 是否多选文件 */
    multiple: Boolean,
    /** todo: 是否可拖拽文件上传 */
    drag: pt,
    /** todo: 是否可拖拽调整预览的顺序 */
    adjust: pt,
    /** 是否在选择文件后立刻上传 */
    auto: pt,
    /** todo: 是否允许后台上传：submit后出现悬浮窗显示上传进度，可以关闭，上传完后执行onsubmit提交表单 */
    back: Boolean,

    /** 允许上传最大文件数 */
    maxCount: pn,
    /** 允许上传单个文件最大字节数 */
    maxByte: pn,
    /** 允许上传所有文件最大字节数 */
    maxBytes: pn,
    /** <input type="file" :accept="accept"> html标签原生accept，大概格式为 audio/*,video/*,image/*,.csv,MIME_type */
    accept: ep("image/*"),
    /** 选择文件出现错误时，是否对没有错误的文件继续进行操作
     * 例如选择3个文件超出了最大文件数2
     * 若force为true，则继续添加2个文件，1个文件被忽略，onErrorSelect任然触发，参数为1个文件
     * 若force为false，则全部文件被忽略，onErrorSelect触发，参数为3个文件
     */
    force: Boolean,
    /** 提交图片文件的服务器地址(XMLHttpRequest) */
    action: {
      type: String,
      default: "http://127.0.0.1:888/Action/1/UploadFile",
      // required: true,
    },
    /** html标签原生name，提交时也是该字段的名字 */
    name: ep("file"),
    /** 预览图片的object-fit属性 */
    objectFit: ep("cover"),

    /** 选中的全部校验通过的文件
     * 下面的onImage和onRead只能一次处理一个文件
     * 这里可以一次性处理全部的文件
     * resolve返回File[]可以替换原本的数组
     * @function(File[])
     */
    onSelect: pp,
    /** 当选择的文件是图片时触发
     * 可用于对图片尺寸进行检测，头像裁切等操作
     * 可返回false放弃继续上传图片
     * 可返回File,Canvas,Image,Blob,Base64String，最终都将转换成File上传服务器
     * 可返回Promise，resolve返回上面的返回值做出对应处理，reject视为返回false
     * 不返回或返回上述以外的值将不做任何处理
     * @function(Image, File)
     */
    onImage: pp,
    /** 同onImage，只是无论选择什么类型文件都会触发这个事件
     * @function(File)
     */
    onRead: pp,

    /** 上传进度发生改变时触发，file.progress可获得0-100的上传进度
     * @function(file, e)
     */
    onProgress: Function,
    /** 上传一个文件成功，file.value可以获得上传成功时服务端的返回值
     * @function(file)
     */
    onSuccess: Function,
    /** 上传一个文件失败，file.error可以获得上传失败的Error
     * @function(file)
     */
    onFailed: Function,
    /** 上传一个文件结束，成功或者失败，!file.error代表成功
     * @function(file)
     */
    onOver: Function,
    /** 上传全部文件结束，!file.error代表文件成功
     * @function(file[])
     */
    onCompleted: Function,
  },
  computed: {
    canUpload() {
      return this.maxCount <= 0 || this.__modelValue.length < this.maxCount;
    },
    uploading() {
      let ret = [];
      for (let child of this.__modelValue)
        if (child.upload)
          ret.push(ret);
      return ret;
    },
  },
  methods: {
    eFileSelect(e) { this.fileSelect(e.target.files); },
    eFileDrop(e) {
      this.fileSelect(e.dataTransfer.files);
      return false;
    },
    ePreview(i) {},
    eDownload(i) {},
    eDelete(i) {
      this.__modelValue.splice(this.__modelValue.indexOf(i), 1);
      // todo: 添加删除效果
      console.log(this)
    },
    /** 新增文件，可判断文件数量，文件大小，文件后缀 */
    fileSelect(files) {
      console.log(files)
      if (this.maxCount && this.__modelValue.length + files.length > this.maxCount) {
        console.log("超出最大文件数", this.maxCount, "现有", this.__modelValue.length, "新增", files.length)
        return;
      }
      if (this.maxByte > 0) {
        let max = Number(this.maxByte);
        for (let file of files) {
          if (file.size > max) {
            console.log("文件", file.name, "超出单个文件尺寸限制", this.maxByte, "文件尺寸", file.size)
            return;
          }
        }
      }
      if (this.maxBytes > 0) {
        let size = 0;
        for (let file of files)
          size += file.size;
        for (let file of this.__modelValue)
          if (file.file)
            size += file.file.size;
        if (size > this.maxBytes) {
          console.log("文件超出尺寸限制", this.maxBytes, "文件尺寸", size)
          return;
        }
      }
      // todo: 检查文件accept：即便限制了accept，任然可以选择所有文件，而且还可以拖拽

      this.fileRead(files);      
    },
    /** 新增读取文件，可判断图片宽高，视频时长，内容格式 */
    async fileRead(files) {
      if (this.onSelect) {
        let ret = this.onSelect(files);
        if (ret && ret.constructor == Promise)
          await ret.then(i => { if (i) files = i; });
      }
      for (let file of files) {
        let newfile = {};
        newfile.name = file.name;
        newfile.file = file;
        // 如果是图片，赋值url进行展示preview
        if (file.type.indexOf("image/") == 0) {
          newfile.url = URL.createObjectURL(file);
          if (this.onImage) {
            await new Promise((resolve) => {
              let image = new Image();
              image.src = newfile.url;
              image.onload = async () => {
                await FileRead(newfile, () => this.onImage(image, newfile.file))
                resolve();
              }
            })
            if (!newfile.file)
              continue;
          }
        }
        if (this.onRead) {
          await FileRead(newfile, () => this.onRead(newfile.file));
          if (!newfile.file)
            continue;
        }
        // push进去后会变成reactive的对象，所以后面更新也需要传入__modelValue里的对象而不是newfile
        this.__modelValue.push(newfile);
        if (this.auto)
          this.fileUpload(this.__modelValue[this.__modelValue.length - 1])
      }
    },
    /** 开始上传新文件 */
    fileUpload(file) {
      file.progress = 0;
      file.upload = UploadFile(this.action, this.name, file.file, 
        (e) => {
          if (e.lengthComputable && e.total)
            // 赋值progress到file中的变量进而影响视图
            file.progress = e.loaded / e.total;
          if (this.onProgress)
            this.onProgress(file, e);
        },
        (ret) => {
          console.log("success", ret)
          file.value = ret;
          this.complete(file);
        },
        (err) => {
          file.error = err;
          if (this.onFailed)
            this.onFailed(file)
        });
    },
    /** 全部文件上传完毕 */
    complete(file) {
      file.progress = 1;
      if (file.error) {
        if (this.onFailed)
          this.onFailed(file)
      } else {
        if (this.onSuccess)
          this.onSuccess(file)
      }
      if (this.onOver)
        this.onOver(file)
      if (this.onCompleted && this.__modelValue._All(i => i.upload && i.pregress == 1)) {
        this.onCompleted()
      }
    },
    /** 停止上传 */
    cancel() {
      for (let file of this.__modelValue)
        if (file.upload)
          file.upload.abort();
    },
  },
  watch: {
    /** 外部可能对数组进行增减操作 */
    "modelValue.length"(value) {
      if (this.__dirty) return;
      let temp = this.__modelValue;
      this.__modelValue = null;
      this._modelValue = temp;
    },
  },
  mounted() {
  },
})
</script>

<style lang="scss">
.ee-upload {
  display: inline-flex;
  vertical-align: top;

  .item {
    width: 9em;
    height: 9em;
    margin: 0 1em 1em 0;
    border-radius: .5em;
    border: 1px #d9d9d9 solid;
    cursor: pointer;
  }

  .upload {
    border-style: dashed;

    &:hover,&:active,&:focus {
      border-color: #0075ff;
    }

    .ee-upload-icon {
      color: gray;
      font-size: 3.5em;
    }
  }

  .preview {
    border-style: solid;
    position: relative;
    overflow: hidden;

    img {
      width: 100%;
      height: 100%;
    }

    .action-mask,.actions{
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      transition: opacity .3s;
    }
    .hidden {
      opacity: 0;
    }
    .action-mask {
      background-color: #000;
    }
    .actions {
      justify-content: space-around;
      font-size: 1.5em;
      cursor: default;

      .action {
        color: #fff;
        cursor: pointer;
      }
    }

    &:hover .hidden { opacity: .5; }
    &:hover .actions { opacity: 1; }
  }

  input[type="file"] {
    display: none;
  }
}
</style>